package cz.semjobKB.virtuoso.dao;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import cz.semjobKB.virtuoso.document.api.IDocumentDetail;
import cz.semjobKB.virtuoso.document.api.IResourceDetail;
import cz.semjobKB.virtuoso.document.api.ITerminologyDetail;
import cz.semjobKB.virtuoso.document.api.ITripleDbItem;
import cz.semjobKB.virtuoso.document.impl.TripleDbItem;
import cz.semjobKB.virtuoso.utils.ETriplePart;
import cz.semjobKB.virtuoso.utils.VirtuosoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class Search {

	@Autowired
	GraphDetails graphDetails;

	@Autowired
	VirtuosoDao virtuosoDao;

	@Autowired
	VirtuosoUtils virtuosoUtils;

	private String queryTemplate = "SELECT DISTINCT * WHERE { GRAPH ?graph { ?subject ?predicate ?object}}";

	public List<ITripleDbItem> searchTriples(String subject, String predicate,
			String object) {
		boolean isSubjectFilled = subject != null;
		boolean isPredicateFilled = predicate != null;
		boolean isObjectFilled = object != null;

		List<IResourceDetail> subjectDetailsList = new ArrayList<IResourceDetail>();
		List<IResourceDetail> predicateDetailsList = new ArrayList<IResourceDetail>();
		List<IResourceDetail> objectDetailsList = new ArrayList<IResourceDetail>();

		List<ITripleDbItem> triplesList = new ArrayList<ITripleDbItem>();
		if(isSubjectFilled) {
			subjectDetailsList = graphDetails.getResourceDetail(subject, false); 
		}

		if(isPredicateFilled) {
			predicateDetailsList = graphDetails.getResourceDetail(predicate, false); 
		}

		if(isObjectFilled) {
			objectDetailsList = graphDetails.getResourceDetail(object, false); 
		}

		if(!subjectDetailsList.isEmpty() && !predicateDetailsList.isEmpty()) {
			for(IResourceDetail subjectDetail : subjectDetailsList) {
				for(IResourceDetail predicateDetail : predicateDetailsList) {
					String query = queryTemplate;
					query = query.replace("?subject",   "<" +subjectDetail.getResourceUri() + ">");
					query = query.replace("?predicate",  "<" + predicateDetail.getResourceUri() + ">");
					List<IResourceDetail> objectResult = getObjects(query);
					for (IResourceDetail objectDetail : objectResult) {
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}
		if(!subjectDetailsList.isEmpty() && !objectDetailsList.isEmpty()) {
			for(IResourceDetail subjectDetail : subjectDetailsList) {
				for(IResourceDetail objectDetail : objectDetailsList) {
					String query = queryTemplate;
					query = query.replace("?subject",  "<" + subjectDetail.getResourceUri() + ">");
					query = query.replace("?object",  "<" + objectDetail.getResourceUri() + ">");
					List<IResourceDetail> predicateDetails = getPredicates(query);
					for (IResourceDetail predicateDetail : predicateDetails) {
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}
		if(!objectDetailsList.isEmpty() && !predicateDetailsList.isEmpty()) {
			for(IResourceDetail objectDetail : objectDetailsList) {
				for(IResourceDetail predicateDetail : predicateDetailsList) {
					String query = queryTemplate;
					query = query.replace("?object", "<" + objectDetail.getResourceUri() + ">");
					query = query.replace("?predicate", "<" + predicateDetail.getResourceUri() + ">");
					List<IResourceDetail> subjectResult = getSubjects(query);
					for (IResourceDetail subjectDetail : subjectResult) {
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}

		if ((isSubjectFilled && isPredicateFilled) || (isSubjectFilled && isObjectFilled) || (isPredicateFilled && isObjectFilled)) {
			return triplesList;
		}

		if(!subjectDetailsList.isEmpty()) {
			for(IResourceDetail subjectDetail : subjectDetailsList) {
				String query = queryTemplate;
				query = query.replace("?subject", "<" +subjectDetail.getResourceUri() + ">");
				List<IResourceDetail> predicateResult = getPredicates(query);
				for(IResourceDetail predicateDetail: predicateResult) {
					String queryDeep = query.replace("?predicate", "<" + predicateDetail.getResourceUri() + ">");
					List<IResourceDetail> objectResult = getObjects(queryDeep);
					for (IResourceDetail objectDetail : objectResult) {					
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}
		if(!predicateDetailsList.isEmpty()) {
			for(IResourceDetail predicateDetail : predicateDetailsList) {
				String query = queryTemplate;
				query = query.replace("?predicate", "<" + predicateDetail.getResourceUri() + ">");
				List<IResourceDetail> subjectResult = getSubjects(query);				
				for (IResourceDetail subjectDetail : subjectResult) {
					String queryDeep = query.replace("?subject", "<" + subjectDetail.getResourceUri() + ">");
					List<IResourceDetail> objectResult = getObjects(queryDeep);
					for(IResourceDetail objectDetail: objectResult) {
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}
		if(!objectDetailsList.isEmpty()) {
			for(IResourceDetail objectDetail : objectDetailsList) {
				String query = queryTemplate;
				query = query.replace("?object",  "<" +objectDetail.getResourceUri() + ">");
				List<IResourceDetail> predicateResult = getPredicates(query);
				for(IResourceDetail predicateDetail: predicateResult) {
					String queryDeep = query.replace("?predicate", "<" + predicateDetail.getResourceUri()+ ">");
					List<IResourceDetail> subjectResult = getSubjects(queryDeep);
					for (IResourceDetail subjectDetail : subjectResult) {					
						triplesList.add(new TripleDbItem(subjectDetail, predicateDetail, objectDetail));
					}
				}
			}
			return triplesList;
		}
		return null;
	}

	public List<IDocumentDetail> searchDocumentByTriples(String subject, String predicate, String object) {
		boolean isSubjectFilled = subject != null;
		boolean isPredicateFilled = predicate != null;
		boolean isObjectFilled = object != null;

		List<IResourceDetail> subjectDetailsList = new ArrayList<IResourceDetail>();
		List<IResourceDetail> predicateDetailsList = new ArrayList<IResourceDetail>();
		List<IResourceDetail> objectDetailsList = new ArrayList<IResourceDetail>();	

		if(isSubjectFilled) {
			subjectDetailsList = graphDetails.getResourceDetail(subject, false); 
		}

		if(isPredicateFilled) {
			predicateDetailsList = graphDetails.getResourceDetail(predicate, false); 
		}

		if(isObjectFilled) {
			objectDetailsList = graphDetails.getResourceDetail(object, false); 
		}

		if ((subjectDetailsList.size() > 0) && (predicateDetailsList.size() > 0) && (objectDetailsList.size() > 0)) {
			return searchByFullTriple(subjectDetailsList, predicateDetailsList, objectDetailsList);
		}
		if ((subjectDetailsList.size() > 0) && (predicateDetailsList.size() > 0)) {
			return searchDocumentBySubPred(subjectDetailsList, predicateDetailsList);
		}
		if ((subjectDetailsList.size() > 0) && (objectDetailsList.size() > 0)) {
			return searchDocumentBySubObj(subjectDetailsList, objectDetailsList);
		}
		if ((predicateDetailsList.size() > 0) && (objectDetailsList.size() > 0)) {
			return searchDocumentByPredObj(predicateDetailsList, objectDetailsList);
		}
		if (subjectDetailsList.size() > 0) {
			return searchDocumentByPart(subjectDetailsList, ETriplePart.SUBJECT);
		}
		if (predicateDetailsList.size() > 0) {
			return searchDocumentByPart(predicateDetailsList, ETriplePart.PREDICATE);
		}
		if (objectDetailsList.size() > 0) {
			return searchDocumentByPart(objectDetailsList, ETriplePart.OBJECT);
		}
		return null;
	}
	
	public List<IDocumentDetail> searchByTerminology(String terminologyName, double frequencyLimit) {
		
		List<ITerminologyDetail> terminologyDetails = new ArrayList<ITerminologyDetail>();		
		if (terminologyName != null) {
			terminologyDetails = graphDetails.getTerminologyDetail(terminologyName);
		}
		
		if(frequencyLimit < 0 ) {
			frequencyLimit = 0;
		}
		
		if(frequencyLimit > 1 ) {
			frequencyLimit = 1;
		}
		
		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();				
		
		Map<String, Integer> graphsUri = new HashMap<String, Integer>();

		for (ITerminologyDetail terminologyDetail : terminologyDetails) {
			String query = "SELECT DISTINCT ?graph ?frequency WHERE { GRAPH ?graph {" +
					"<" + virtuosoUtils.escape(terminologyDetail.getResourceUri()) +">" +
					" <"+ virtuosoUtils.getOntologyNodes().get("hasFrequency") +"> " +
					" ?frequency }}";
			ResultSet result = virtuosoDao.executeQuery(query, null);
			while(result.hasNext()) {
				QuerySolution solution = result.next();								
				graphsUri.put(solution.getResource("graph").getURI(), 
						Integer.valueOf(solution.getLiteral("frequency").getString()));			
			}
			
			graphsUri = virtuosoUtils.sortByValues(graphsUri);
			
			Set<String> resultGraphsUris = new HashSet<String>();
			for(Map.Entry<String, Integer> entry : graphsUri.entrySet()) {
				if ((entry.getValue() / terminologyDetail.getOverallFrequency()) >= frequencyLimit) {
					resultGraphsUris.add(entry.getKey());
				}
			}
			for(String GraphUri : resultGraphsUris) {
				documentDetails.add(graphDetails.getDocumentDetail(GraphUri));
			}
			graphsUri.clear();
		}
		return documentDetails;
	}

	private List<IDocumentDetail> searchByFullTriple (List<IResourceDetail> subjectDetailsList,
			List<IResourceDetail> predicateDetailsList, List<IResourceDetail> objectDetailsList) {

		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();

		Set<String> graphsUri = new HashSet<String>();

		for(IResourceDetail subjectDetail : subjectDetailsList) {
			for (IResourceDetail predicateDetail : predicateDetailsList) {
				for (IResourceDetail objectDetail : objectDetailsList) {

					String query = "SELECT DISTINCT ?graph WHERE { GRAPH ?graph {" +
							"<" + virtuosoUtils.escape(subjectDetail.getResourceUri()) +">" +
							"<" + virtuosoUtils.escape(predicateDetail.getResourceUri()) +">" +
							"<" + virtuosoUtils.escape(objectDetail.getResourceUri()) +">" +"}}";

					ResultSet result = virtuosoDao.executeQuery(query, null);
					while(result.hasNext()) {
						QuerySolution solution = result.next();
						graphsUri.add(solution.getResource("graph").getURI());
					}
				}
			}
		}

		for(String graphUri : graphsUri) {
			documentDetails.add(graphDetails.getDocumentDetail(graphUri));
		}

		return documentDetails;		
	}

	private List<IDocumentDetail> searchDocumentBySubPred(List<IResourceDetail> subjectDetailsList, List<IResourceDetail> predicateDetailsList) {

		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();		

		Set<String> graphsUri = new HashSet<String>();

		for(IResourceDetail subjectDetail : subjectDetailsList) {
			for (IResourceDetail predicateDetail : predicateDetailsList) {
				String query = "SELECT DISTINCT ?graph WHERE { GRAPH ?graph {" +
						"<" + virtuosoUtils.escape(subjectDetail.getResourceUri()) +">" +
						"<" + virtuosoUtils.escape(predicateDetail.getResourceUri()) +">" +
						"?o}}";

				ResultSet result = virtuosoDao.executeQuery(query, null);
				while(result.hasNext()) {
					QuerySolution solution = result.next();
					graphsUri.add(solution.getResource("graph").getURI());
				}
			}
		}

		for(String graphUri : graphsUri) {
			documentDetails.add(graphDetails.getDocumentDetail(graphUri));
		}		
		return documentDetails;
	}

	private List<IDocumentDetail> searchDocumentByPredObj(List<IResourceDetail> predicateDetailsList, List<IResourceDetail> objectDetailsList) {

		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();		
		Set<String> graphsUri = new HashSet<String>();

		if ((predicateDetailsList.size() > 0) && (objectDetailsList.size() > 0)) {
			for (IResourceDetail predicateDetail : predicateDetailsList) {
				for (IResourceDetail objectDetail : objectDetailsList) {

					String query = "SELECT DISTINCT ?graph WHERE { GRAPH ?graph {" +
							" ?s " +
							"<" + virtuosoUtils.escape(predicateDetail.getResourceUri()) +">" +
							"<" + virtuosoUtils.escape(objectDetail.getResourceUri()) +">" +"}}";

					ResultSet result = virtuosoDao.executeQuery(query, null);
					while(result.hasNext()) {
						QuerySolution solution = result.next();
						graphsUri.add(solution.getResource("graph").getURI());
					}
				}
			}
		}		

		for(String graphUri : graphsUri) {
			documentDetails.add(graphDetails.getDocumentDetail(graphUri));
		}

		return documentDetails;		
	}

	private List<IDocumentDetail> searchDocumentBySubObj(List<IResourceDetail> subjectDetailsList, List<IResourceDetail> objectDetailsList) {

		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();

		Set<String> graphsUri = new HashSet<String>();

		for(IResourceDetail subjectDetail : subjectDetailsList) {				
			for (IResourceDetail objectDetail : objectDetailsList) {

				String query = "SELECT DISTINCT ?graph WHERE { GRAPH ?graph {" +
						"<" + virtuosoUtils.escape(subjectDetail.getResourceUri()) +">" +
						" ?p " +
						"<" + virtuosoUtils.escape(objectDetail.getResourceUri()) +">" +"}}";

				ResultSet result = virtuosoDao.executeQuery(query, null);
				while(result.hasNext()) {
					QuerySolution solution = result.next();
					graphsUri.add(solution.getResource("graph").getURI());
				}
			}
		}


		for(String graphUri : graphsUri) {
			if (!isKnownGraph(graphUri)) {
				documentDetails.add(graphDetails.getDocumentDetail(graphUri));
			}
		}

		return documentDetails;		
	}

	private List<IDocumentDetail> searchDocumentByPart(List<IResourceDetail> resourceDetails, ETriplePart triplePart) {

		String query = "SELECT DISTINCT ?graph WHERE { GRAPH ?graph { ?s ?p ?o}}";
		List<IDocumentDetail> documentDetails = new ArrayList<IDocumentDetail>();
		Set<String> graphsUri = new HashSet<String>();

		switch (triplePart.toString()) {
		case "SUBJECT" : { 
			resourceDetails = filtherDetails(resourceDetails, virtuosoUtils.getKnownGraphsUris().get("terms")); 
			for (IResourceDetail resourceDetail : resourceDetails) {
				query = query.replace("?s", "<" + resourceDetail.getResourceUri() + ">");
				ResultSet result = virtuosoDao.executeQuery(query, null);
				while(result.hasNext()) {
					QuerySolution solution = result.next();
					graphsUri.add(solution.getResource("graph").getURI());
				}
			}
			
			break;
		}
		case "PREDICATE" : { 
			resourceDetails = filtherDetails(resourceDetails, virtuosoUtils.getKnownGraphsUris().get("predicates")); 
			for (IResourceDetail resourceDetail : resourceDetails) {
				query = query.replace("?p", "<" + resourceDetail.getResourceUri() + ">");
				ResultSet result = virtuosoDao.executeQuery(query, null);
				while(result.hasNext()) {
					QuerySolution solution = result.next();
					graphsUri.add(solution.getResource("graph").getURI());
				}
			}
			break;
		}
		case "OBJECT" : { 
			resourceDetails = filtherDetails(resourceDetails, virtuosoUtils.getKnownGraphsUris().get("terms")); 
			for (IResourceDetail resourceDetail : resourceDetails) {
				query = query.replace("?o", "<" + resourceDetail.getResourceUri() + ">");
				ResultSet result = virtuosoDao.executeQuery(query, null);
				while(result.hasNext()) {
					QuerySolution solution = result.next();
					graphsUri.add(solution.getResource("graph").getURI());
				}
			}
			break;
		}
		}

		for(String graphUri : graphsUri) {
			if (!isKnownGraph(graphUri)) {
				documentDetails.add(graphDetails.getDocumentDetail(graphUri));
			}
		}

		return documentDetails;
	}
	
	private List<IResourceDetail> filtherDetails(List<IResourceDetail> resources, String prefix) {
		List<IResourceDetail> filtheredResources = new ArrayList<IResourceDetail>();
		for (IResourceDetail resourceDetail : resources) {
			if (resourceDetail.getResourceUri().startsWith(prefix)) {
				filtheredResources.add(resourceDetail);
			}
		}
		return filtheredResources;		
	}

	private List<IResourceDetail> getObjects(String query) {
		ResultSet result = virtuosoDao.executeQuery(query, null);
		List<IResourceDetail> objectDetails = new ArrayList<IResourceDetail>();
		Set<String> uriUniqueSet = new HashSet<String>();
		while(result.hasNext()) {
			QuerySolution solution = result.next();
			if(isKnownGraph(solution.getResource("?graph").getURI())) {
				continue;
			}
			Node objectUri = solution.getResource("?object").asNode();
			uriUniqueSet.add(objectUri.getURI());						
		}
		for(String uri : uriUniqueSet) {
			objectDetails.addAll(graphDetails.getResourceDetail(uri, true));
		}
		return objectDetails;		
	}

	private List<IResourceDetail> getPredicates(String query) {
		ResultSet result = virtuosoDao.executeQuery(query, null);
		List<IResourceDetail> predicateDetails = new ArrayList<IResourceDetail>();
		Set<String> uriUniqueSet = new HashSet<String>();
		while(result.hasNext()) {
			QuerySolution solution = result.next();
			if(isKnownGraph(solution.getResource("?graph").getURI())) {
				continue;
			}
			Node predicateUri = solution.getResource("?predicate").asNode();
			uriUniqueSet.add(predicateUri.getURI());						
		}
		for(String uri : uriUniqueSet) {
			predicateDetails.addAll(graphDetails.getResourceDetail(uri, true));
		}
		return predicateDetails;		
	}

	private List<IResourceDetail> getSubjects(String query) {
		ResultSet result = virtuosoDao.executeQuery(query, null);
		List<IResourceDetail> subjectDetails = new ArrayList<IResourceDetail>();
		Set<String> uriUniqueSet = new HashSet<String>();
		while(result.hasNext()) {
			QuerySolution solution = result.next();
			if(isKnownGraph(solution.getResource("?graph").getURI())) {
				continue;
			}
			Node subjectUri = solution.getResource("?subject").asNode();
			uriUniqueSet.add(subjectUri.getURI());			
		}
		for(String uri : uriUniqueSet) {
			subjectDetails.addAll(graphDetails.getResourceDetail(uri, true));
		}
		return subjectDetails;		
	}

	private boolean isKnownGraph(String uri) {
		Map<String, String> knownGraphs = new HashMap<String, String>(virtuosoUtils.getKnownGraphsUris());
		knownGraphs.remove("document");
		for (Map.Entry<String, String> entry : knownGraphs.entrySet()) {
			if (uri.startsWith(entry.getValue())) {
				return true;
			}
		}
		return false;
	}
}
